// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <author name="Daniel Grunwald"/>
//     <version>$Revision: 5572 $</version>
// </file>

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Utils;
// ReSharper disable RedundantThisQualifier
namespace ICSharpCode.AvalonEdit.Highlighting
{
    /// <summary>
    /// Manages a list of syntax highlighting definitions.
    /// </summary>
    /// <remarks>
    /// All memers on this class (including instance members) are thread-safe.
    /// </remarks>
    public class HighlightingManager : IHighlightingDefinitionReferenceResolver
    {
        private sealed class DelayLoadedHighlightingDefinition : IHighlightingDefinition
        {
            private readonly object _lockObj = new object();
            private readonly string _name;
            private Func<IHighlightingDefinition> _lazyLoadingFunction;
            private IHighlightingDefinition _definition;
            private Exception _storedException;

            public DelayLoadedHighlightingDefinition(string name, Func<IHighlightingDefinition> lazyLoadingFunction)
            {

                this._name = name;
                this._lazyLoadingFunction = lazyLoadingFunction;
            }

            public string Name
            {
                get { return _name ?? GetDefinition().Name; }
            }

            public XshdSyntaxDefinition Definition { get; set; }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
                "CA1031:DoNotCatchGeneralExceptionTypes",
                Justification = "The exception will be rethrown")]
            private IHighlightingDefinition GetDefinition()
            {
                Func<IHighlightingDefinition> func;
                lock (_lockObj)
                {
                    if (this._definition != null)
                        return this._definition;
                    func = this._lazyLoadingFunction;
                }
                Exception exception = null;
                IHighlightingDefinition def = null;
                try
                {
                    using (var busyLock = BusyManager.Enter(this))
                    {
                        if (!busyLock.Success)
                            throw new InvalidOperationException(
                                "Tried to create delay-loaded highlighting definition recursively. Make sure the are no cyclic references between the highlighting definitions.");
                        def = func();
                    }
                    if (def == null)
                        throw new InvalidOperationException(
                            "Function for delay-loading highlighting definition returned null");
                }
                catch (Exception ex)
                {
                    exception = ex;
                }
                lock (_lockObj)
                {
                    this._lazyLoadingFunction = null;
                    if (this._definition == null && this._storedException == null)
                    {
                        this._definition = def;
                        this._storedException = exception;
                    }
                    if (this._storedException != null)
                        throw new HighlightingDefinitionInvalidException("Error delay-loading highlighting definition",
                                                                         this._storedException);
                    return this._definition;
                }
            }

            public HighlightingRuleSet MainRuleSet
            {
                get { return GetDefinition().MainRuleSet; }
            }

            public HighlightingRuleSet GetNamedRuleSet(string name)
            {
                return GetDefinition().GetNamedRuleSet(name);
            }

            public HighlightingColor GetNamedColor(string name)
            {
                return GetDefinition().GetNamedColor(name);
            }

            public IEnumerable<HighlightingColor> NamedHighlightingColors
            {
                get { return GetDefinition().NamedHighlightingColors; }
            }

            public override string ToString()
            {
                return this.Name;
            }
        }


        private readonly object _lockObj = new object();

        private readonly Dictionary<string, IHighlightingDefinition> _highlightingsByName =
            new Dictionary<string, IHighlightingDefinition>();

        private readonly Dictionary<string, IHighlightingDefinition> _highlightingsByExtension =
            new Dictionary<string, IHighlightingDefinition>(StringComparer.OrdinalIgnoreCase);

        private readonly List<IHighlightingDefinition> _allHighlightings = new List<IHighlightingDefinition>();

        /// <summary>
        /// Gets a highlighting definition by name.
        /// Returns null if the definition is not found.
        /// </summary>
        public IHighlightingDefinition GetDefinition(string name)
        {
            lock (_lockObj)
            {
                IHighlightingDefinition rh;
                return _highlightingsByName.TryGetValue(name, out rh) ? rh : null;
            }
        }

        /// <summary>
        /// Gets a copy of all highlightings.
        /// </summary>
        public ReadOnlyCollection<IHighlightingDefinition> HighlightingDefinitions
        {
            get
            {
                lock (_lockObj)
                {
                    return Array.AsReadOnly(_allHighlightings.ToArray());
                }
            }
        }

        /// <summary>
        /// Gets the names of the registered highlightings.
        /// </summary>
        [ObsoleteAttribute("Use the HighlightingDefinitions property instead.")]
        public IEnumerable<string> HighlightingNames
        {
            get
            {
                lock (_lockObj)
                {
                    return new List<string>(_highlightingsByName.Keys);
                }
            }
        }

        /// <summary>
        /// Gets a highlighting definition by extension.
        /// Returns null if the definition is not found.
        /// </summary>
        public IHighlightingDefinition GetDefinitionByExtension(string extension)
        {
            lock (_lockObj)
            {
                IHighlightingDefinition rh;
                return _highlightingsByExtension.TryGetValue(extension, out rh) ? rh : null;
            }
        }

        /// <summary>
        /// Gets the default extensions.
        /// </summary>
        /// <returns></returns>
        public string[] GetDefaultExtensions()
        {
            return _highlightingsByExtension.Select(h => h.Key).ToArray();
        }

        /// <summary>
        /// Registers a highlighting definition.
        /// </summary>
        /// <param name="name">The name to register the definition with.</param>
        /// <param name="extensions">The file extensions to register the definition for.</param>
        /// <param name="highlighting">The highlighting definition.</param>
        public void RegisterHighlighting(string name, string[] extensions, IHighlightingDefinition highlighting)
        {
            if (highlighting == null)
                throw new ArgumentNullException("highlighting");

            lock (_lockObj)
            {
                if (_allHighlightings.FirstOrDefault(h => h.Name == name) != null)
                    return;
                _allHighlightings.Add(highlighting);
                if (name != null)
                {
                    _highlightingsByName[name] = highlighting;
                }
                if (extensions != null)
                {
                    foreach (string ext in extensions)
                    {
                        _highlightingsByExtension[ext] = highlighting;
                    }
                }
            }
        }

        /// <summary>
        /// Registers the highlighting.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        public void RegisterHighlighting(string fileName)
        {
            var definition = HighlightingLoader.LoadXshdDefinition(fileName);
            var hightlight = HighlightingLoader.LoadHighlightingDefinition(fileName);
            var keywords = definition.Keywords.Distinct().ToList();
            foreach (var keyword in keywords.ToList())
            {
                var lowerKeyword = keyword.ToLower();
                if (keywords.Contains(lowerKeyword)) continue;
                keywords.Add(lowerKeyword);
            }

            keywords.Sort();
            definition.Keywords = keywords;
            hightlight.Definition = definition;
            Instance.RegisterHighlighting(definition.Name, definition.Extensions.ToArray(), hightlight);
        }

        /// <summary>
        /// Registers a highlighting definition.
        /// </summary>
        /// <param name="name">The name to register the definition with.</param>
        /// <param name="extensions">The file extensions to register the definition for.</param>
        /// <param name="lazyLoadedHighlighting">A function that loads the highlighting definition.</param>
        public void RegisterHighlighting(string name, string[] extensions,
                                         Func<IHighlightingDefinition> lazyLoadedHighlighting)
        {
            if (lazyLoadedHighlighting == null)
                throw new ArgumentNullException("lazyLoadedHighlighting");
            RegisterHighlighting(name, extensions, new DelayLoadedHighlightingDefinition(name, lazyLoadedHighlighting));
        }

        /// <summary>
        /// Gets the default HighlightingManager instance.
        /// The default HighlightingManager comes with built-in highlightings.
        /// </summary>
        public static HighlightingManager Instance
        {
            get { return DefaultHighlightingManager.Instance; }
        }

        internal sealed class DefaultHighlightingManager : HighlightingManager
        {
            public new static readonly DefaultHighlightingManager Instance = new DefaultHighlightingManager();

            public DefaultHighlightingManager()
            {
                // Resources.RegisterBuiltInHighlightings(this);
            }

            // Registering a built-in highlighting
            internal void RegisterHighlighting(string name, string[] extensions, string resourceName)
            {
                try
                {
#if DEBUG
    // don't use lazy-loading in debug builds, show errors immediately
					Xshd.XshdSyntaxDefinition xshd;
					using (Stream s = Resources.OpenStream(resourceName)) {
						using (XmlTextReader reader = new XmlTextReader(s)) {
							xshd = Xshd.HighlightingLoader.LoadXshd(reader, false);
						}
					}
					Debug.Assert(name == xshd.Name);
					if (extensions != null)
						Debug.Assert(System.Linq.Enumerable.SequenceEqual(extensions, xshd.Extensions));
					else
						Debug.Assert(xshd.Extensions.Count == 0);
					
					// round-trip xshd:
//					string resourceFileName = Path.Combine(Path.GetTempPath(), resourceName);
//					using (XmlTextWriter writer = new XmlTextWriter(resourceFileName, System.Text.Encoding.UTF8)) {
//						writer.Formatting = Formatting.Indented;
//						new Xshd.SaveXshdVisitor(writer).WriteDefinition(xshd);
//					}
//					using (FileStream fs = File.Create(resourceFileName + ".bin")) {
//						new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter().Serialize(fs, xshd);
//					}
//					using (FileStream fs = File.Create(resourceFileName + ".compiled")) {
//						new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter().Serialize(fs, Xshd.HighlightingLoader.Load(xshd, this));
//					}
					
					RegisterHighlighting(name, extensions, Xshd.HighlightingLoader.Load(xshd, this));
#else
                    RegisterHighlighting(name, extensions, LoadHighlighting(resourceName));
#endif
                }
                catch (HighlightingDefinitionInvalidException ex)
                {
                    throw new InvalidOperationException("The built-in highlighting '" + name + "' is invalid.", ex);
                }
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
                Justification = "LoadHighlighting is used only in release builds")]
            private Func<IHighlightingDefinition> LoadHighlighting(string resourceName)
            {
                Func<IHighlightingDefinition> func =
                    delegate
                        {
                            Xshd.XshdSyntaxDefinition xshd;
                            using (var s = Resources.OpenStream(resourceName))
                            {
                                using (var reader = new XmlTextReader(s))
                                {
                                    // in release builds, skip validating the built-in highlightings
                                    xshd = HighlightingLoader.LoadXshd(reader, true);
                                }
                            }

                            return HighlightingLoader.Load(xshd, this);
                        };

                return func;
            }
        }
    }
}
